简体   繁体   中英

REACT - REDUX how to update the global state of a redux app in child component that fetched data in parent when app is mounted

I have a react application that fetch data with redux thunk and set it as global state from a main component when the app is mounted then I have a child component where a form is filled and then I want to update the state based on the input in this form and then redirects to another component with the global state updated.

the main component is this:

mainComponent.js

import React, { Component } from 'react';
import {Routes, Route, Navigate, useParams, useNavigate} from 'react-router-dom'; //Switch changed to routes Also redirect is changed to Navigate since version 6
import {connect} from 'react-redux';
import { useLocation } from 'react-router-dom';

import Store from './store-components/StoreComponent';



import {fetchProducts} from '../redux/ActionCreators';

// --------Hook to use withRouter from v5 in actual v6-----------------
export const withRouter = (Component) => {
  const Wrapper = (props) => {
    const navigate = useNavigate();
    const location = useLocation();
    const params = useParams();
    
    return (
      <Component
        navigate={navigate}
        location={location}
        params={params}
        {...props}
        />
    );
  };
  
  return Wrapper;
};


const mapStateToProps = (state) => {
    return{
        products: state.products,
    }
}

const mapDispatchToProps = dispatch => ({
  fetchProducts: () => { dispatch(fetchProducts())},
  
  
});



class Main extends Component {

  componentDidMount() {
    this.props.fetchProducts(); 
  
  }
  
  render(){
  
    
    
  return (  
    
    <div>
      <Header/>  
      <Routes>
        <Route path = "/login" element = {<LoginComponent/>}/>
        <Route path="/home" element={<Home products={this.props.products}/>}/> 

        <Route exact path="/store" element= {<Store products={this.props.products} />} />  
        <Route path="*"element={<Navigate to="/home" />} />
        {/* Instead of redirect the above is needed to redirect if there is no matched url*/}
      </Routes>
      
      <Footer location={this.props.location}/>
    </div>
  );
}
};



export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Main));

and the reducer that fetch the products:

ActionCreator.js

import * as ActionTypes from './ActionTypes';
import { baseUrl } from '../shared/baseUrl';




// -------------------------- products--------------------------------
//-- products thunk
export const fetchProducts = () => (dispatch) => {

    dispatch(productsLoading(true));

    return fetch(baseUrl +'products')
        .then(response => {
            if (response.ok){
                return response;
            }       
            else{
                var error = new Error('Error '+response.status+': '+response.statusText)
                error.response = response;
                throw error;
            }

        },
        error =>{
            var errmess=new Error(error.message);
            throw errmess;
        })
        .then(response => response.json())
        .then(products => dispatch(addProducts(products)))
        .catch(error => dispatch(productsFailed(error.message)));
}
// thunk


// this is something I tried to solve my problem but is not working

export const fetchProductsBuscador = (param) => (dispatch) => {

    dispatch(productsLoading(true));

    return fetch(baseUrl +'products'+'/'+param)
        .then(response => {
            if (response.ok){
                return response;
            }       
            else{
                var error = new Error('Error '+response.status+': '+response.statusText)
                error.response = response;
                throw error;
            }

        },
        error =>{
            var errmess=new Error(error.message);
            throw errmess;
        })
        .then(response => response.json())
        .then(products=> dispatch(addProducts(products)))
        .catch(error => dispatch(productsFailed(error.message)));
}

and the component where the form is in which i want to update my state is

Finder.js

import React, {Component} from 'react';
//this is a service to use navigate in this class component in order to redirect to the //component i want to render the updated state
import { withNavigate } from '../../services/withNavigate';
import { Navigate } from 'react-router-dom';
import { connect } from 'react-redux';

import {fetchProductsBuscador} from '../../redux/ActionCreators';
// this component will be used to search for a product
// the user will select the type of product, marca, linea, modelo,

// the server will handle the search and return the products that match the search criteria by using query parameters
// so the first input will be the tipo, so when the user selects a tipo, the server will return the marcas that are available for that tipo
// then the user will select a marca, and the server will return the lineas that are available for that marca


function toTitleCase(str) {
    return str.replace(/\w\S*/g, function(txt){
        return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
    });
}


const mapStateToProps = (state) => {
    return{
        products: state.products,
    }
}

const mapDispatchToProps = dispatch => ({
  fetchProductsBuscador: (param) => { dispatch(fetchProductsBuscador(param))},

});

class Finder extends Component {
    constructor(props){
        super(props);        
        this.state = {
            tipo: '',
            marca: '',
            linea: '',
            marcaDropdown: [],
            lineaDropdown: [],
            modeloDropdown: [],
            modeloInput: '',

            // the url will be updated with the new values
            url: baseUrl+'buscaproduct'
        };
        // bind the functions handlers to the constructor to make them available
        this.handleSubmit = this.handleSubmit.bind(this);
        this.handleInputChange = this.handleInputChange.bind(this);

    }
    
    handleSubmit(event){
        event.preventDefault()

        // redirect to the store component with the search criteria
        // the search criteria will be passed as query parameters
        var tipo  = this.state.tipo
        var marca = toTitleCase(this.state.marca)
        var linea = this.state.linea

        this.props.fetchProductsBuscador('/?tipo=' + tipo + '&marca=' + marca + '&linea=' + linea)
        .then((response)=>{
            this.props.navigate('/store',{
                state:{
                    products:response.data
                }
            })
        }).catch((error)=>{
            console.log(error)
        });

            
    }
    

    // this function will handle the input change
    // when a type is selected, the marca will be updated and the url will be updated,
    // an async call will be made to the server to get the marcas that are available for that tipo
    // then the marca will be updated with the data from the server
    // and the url will be updated with the new marca
    // and so on

    handleInputChange(event){
        const target = event.target;
        const value = target.value;
        const name = target.name;
        this.setState({
            [name]: value
        });

        // if the user selects a tipo, then the marca will be updated with the marcas that are available for that tipo
        // and the url will be updated with the new marca
        if(name === 'tipo'){
            axios.get(baseUrl+'buscavehiculo/?tipo=' + value)
            .then((response) => {
                this.setState({
                    marcaDropdown: response.data
                });
            console.log('marcas',response.data);
            })
            .catch((error) => {
                console.log(error);
            });
        }

        // if the user selects a marca, then the linea will be updated with the lineas that are available for that marca
        // and the url will be updated with the new linea
        if(name === 'marca'){
            axios.get(baseUrl+'buscavehiculo/?tipo=' +this.state.tipo + '&marca=' + value)
            .then((response) => {
                this.setState({
                    lineaDropdown: response.data
                });
            })
            .catch((error) => {
                console.log(error);
            });
        }
    }

    render(){
        return(
        // HERE IS the form i suppose is not needed to show as this just give the values of //above 
        );        
    }

}

export default withNavigate(connect(mapStateToProps,mapDispatchToProps) (Finder));

another approach that i tried was with axios in the handleSubmit :

handleSubmit(event){
        event.preventDefault()

        // redirect to the store component with the search criteria
        // the search criteria will be passed as query parameters
        var tipo  = this.state.tipo
        var marca = toTitleCase(this.state.marca)
        var linea = this.state.linea

        
         axios.get(baseUrl+'products' + '/?tipo=' + tipo + '&marca=' + marca + '&linea=' + linea )
         .then((response) => {
             console.log('response.data',response.data)
             this.props.navigate("/store",{
                 state:{
                     products:response.data
                 }
             });
     
         })
         .catch((error) => {
             console.log(error)
         })
    
    }
   
 

the above can redirect but render the data that was fetched with redux thunk the first time the app was mounted and not the updated one with axios, axios did the work of fetching the filtered data but cant be updated the state and with the redux approach i got the error

Uncaught TypeError: can't access property "then", this.props.fetchProductsBuscador(...) is undefined

how can i update the state in order that when I redirect in the handleSubmit just the data that i want and that was filtered in the form is rendered and not the data that was first fetched?

Unless you are in a pre-2019 codebase that never updated to React 16.8, please do not write class components and please dont't use connect and mapStateToProps .
Class components are a legacy API and connect only exists for backwards compatibility with those.

Nowadays, you should be using the useSelector and useDispatch hooks and you should be using those in all your components that need it - there is no distinction between dispatching an action in a parent or child component - just dispatch an action wherever you need it and just subscribe to store values with useSelector wherever you need it.

All that said, with all this you are probably also using a very outdated style of Redux, even though you are not showing it here: modern Redux does not use hand-written reducers with switch statements and ACTION_TYPE string constants. createSlice handles all that for you.
Also, you don't need to write fetching logic by hand like you do it here, RTK Query handles that part for you - and "on accident" it will also handle your question here - your cache entries will be separated by filter and when you re-mount your original component with different filters, it will not show the old values first.

Generally, I would highly recommend you to read why Redux Toolkit is how to use Redux today and then follow the official Redux Tutorial as it seems that the sources you are following are outdated by over three years.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM