Search code examples
javascriptreactjsstatesetstate

React Context with Hooks is always "one step behind"


I am coding a computer comparison website and I encountered an issue when setting filters for the API call. It seems to always be "one step behind" when I add a filter. For example, I add the filter for "orderby" and then call getComputers(), which fetches the pcs from our api using the filters as parameters, but it doesn't apply any parameters, as the filters are still empty. If I do it again with a different value for "orderby" it takes the value from before as param for the api call. I don't understand why this works the way it does and would really appreciate any help :)

Yes, I did research on this before, but nothing seemed to work for me...

Here is my code:

The search results page:

import React from 'react';
import { useContext } from 'react';
import SearchContext from '../../context/search/SearchContext';
import Navbar from '../layout/Navbar';
import Computers from '../computer/Computers';
import Select from 'react-select';

const SearchResults = () => {
  const searchContext = useContext(SearchContext);
  const { filters, orders, getComputers, addFilter } = searchContext;

  return (
    <>
      <Navbar activeItem='Search'></Navbar>

      <div className='container my-3 text-dark'>
        <div className='row'>
          <div className='col-lg-9 col-md-6'>
            <h1 className='display-3 mt-3'>Suche</h1>
          </div>
          <div className='col-lg-3 col-md-6'>
            <Select
              placeholder='Sortierung'
              options={orders.map(order => {
                return { value: order.value, label: order.trans };
              })}
              onChange={val => {
                addFilter({ name: 'orderby', value: val.value });
                getComputers();
              }}
            />
          </div>
        </div>
        <div className='my-3'>
          {filters.map((filter, index) => {
            var text = filter.value.toString();
            return (
              <span
                className='badge badge-pill badge-secondary mx-1 p-2'
                key={index}
              >
                {text.charAt(0).toUpperCase() + text.slice(1)}
              </span>
            );
          })}
        </div>

        <Computers></Computers>
      </div>
    </>
  );
};

export default SearchResults;

Strangely, my page displays the current "orderby" filter correctly in the little pills (see line 42). But the API call doesn't take the right filter...

Relevant parts of my Context:

SearchState.js:

const [state, dispatch] = useReducer(SearchReducer, initialState);

// ACTIONS

  // Add filter
  const addFilter = filter => {
    console.log(state.filters);
    dispatch({ type: ADD_FILTER, payload: filter });
  };

// Get PCs from API
  const getComputers = async () => {
    try {
      state.loading = true;
      console.log(state.filters);
      let params = {};
      state.filters.forEach(filter => {
        // if (filter.name !== 'usecase' && filter.name !== 'type')
        //only as long as usecase and type are not implemented in api yet
        if (!params[filter.name]) params[filter.name] = '';
        params[filter.name] = filter.value;
      });
      console.log(params);
      const res = await axios.get(
        'notgonnatellumyurlxd',
        {
          params
        }
      );
      console.log(res);
      dispatch({ type: GET_COMPUTERS, payload: res.data });
    } catch (err) {
      console.log(err);
    }
  };

SearchReducer.js:

export default (state, action) => {
  switch (action.type) {
    case ADD_FILTER:
      // Used for deciding whether to update existing filter or set new
      var update = false;
      state.filters.forEach(filter => {
        if (filter.name === action.payload.name) {
          update = true;
        }
      });
      return {
        ...state,
        filters: !update
          ? [...state.filters, action.payload]
          : state.filters.map(filter =>
              filter.name === action.payload.name ? action.payload : filter
            )
      };
.
.
.
case GET_COMPUTERS:
      return {
        ...state,
        computers: action.payload,
        loading: false
      };
};

I am really struggling with this right now and hope to find some help here. Big thanks in advance! You people on here are great :)


Solution

  • getComputers works on other state "instance"/ref`

    You can use useEffect as addFilter changes filter:

    useEffect(() => {
      getComputers();
    }, [filter]);
    
    onChange={val => addFilter({ name: 'orderby', value: val.value })}
    

    addFilter changes filter ... filter change runs effect code (every time it changes)