How to make some variables available in a redux thunk - javascript

I have a redux thunk createTodoThunk.
This thunk needs two variables accountIntId and todoListIntId required as placeholders to build the URL of the endpoint to call to create the todo.
In practice, I need that an URL changes from this to this:
- /api/accounts/:account/todo_lists/:todo_list/todos
+ /api/accounts/1/todo_lists/1/todos
The question is: how can I pass these two variables to the thunk?
More details about the thunk later.
For the moment lets go in order...
I have a form to create a todo:
const TodoForm = ({ handleSubmit, isWorking, scope, onToggle }) => {
const idPrefix = 'todo';
return (
<form
onSubmit={handleSubmit}
>
<Field
component={RenderField}
name="name"
type="text"
idPrefix={idPrefix}
/>
<Field
component={RenderField}
name="description"
type="textarea"
idPrefix={idPrefix}
/>
<button type="submit">
{'creating' === scope ? 'Create new todo' : 'Edit todo'}
{isWorking && <Spinner />}
</button>
...
</form>
);
};
TodoForm.propTypes = {
...
};
function mapStateToProps(state, ownProps) {
...
return { isWorking };
}
function mapDispatchToProps(dispatch, ownProps) {
...
return { onCancel };
}
const ConnectedForm = connect(mapStateToProps, mapDispatchToProps)(TodoForm);
export default reduxForm({ form: 'todo_quick' })(ConnectedForm);
This component is used by other components that show the form to create the Todo (component TodoForm).
One of those components is TodoListPage:
class TodoListPage extends React.Component {
componentDidMount() {
...
}
render() {
const {
currentAccountIntId,
currentTodoListModel,
...
onCreateTodo,
...
} = this.props;
...
return hasToRedirect ? (
...
) : (
...
{... &&
(showTodoForm ? (
<TodoForm
onSubmit={onCreateTodo}
...
/>
) : (
...
))}
...
);
}
}
TodoListPage.propTypes = {
currentAccountIntId: PropTypes.number.isRequired,
currentTodoListIntId: PropTypes.number.isRequired,
...
onCreateTodo: PropTypes.func.isRequired,
...
};
TodoListPage.defaultProps = { currentTodoListModel: null };
const mapStateToProps = (state, ownProps) => {
const currentAccountIntId = parseInt(ownProps.match.params[ACCOUNT_KEY], 10);
const currentTodoListIntId = parseInt(ownProps.match.params[TODO_LIST_KEY], 10);
return {
currentAccountIntId,
currentTodoListIntId,
currentTodoListModel: dbGetTodoListDetailsFromIntId(state, currentTodoListIntId),
...
};
};
const mapDispatchToProps = {
onCreateTodo: createTodoThunk,
...
};
export default connect(mapStateToProps, mapDispatchToProps)(TodoListPage);
As you can see, onSubmit={onCreateTodo} actually receives the thunk createTodoThunk (as defined in mapDispatchToProps).
The redux thunk createTodoThunk is this:
export function createTodoThunk(todo) {
return (dispatch, getState) => {
dispatch(createTodoLoadingAction(true));
const token = ctxGetUserToken(getState());
return fetch(TODOS_TODO_CREATE_ENDPOINT, token, null, HTTP_POST, todo)
.then((response) => {
...
};
}
This thunk calls the URL TODOS_TODO_CREATE_ENDPOINT. It is something like this:
/api/accounts/:account/todo_lists/:todo_list/todos
As you can see, it has two placeholders:
:account
:todo_list
So, before calling the endpoint, I need to substitute the two endpoints.
The fetch() function already does this: it is sufficient to pass an array with the substitutions to do:
export function createTodoThunk(todo) {
return (dispatch, getState) => {
dispatch(createTodoLoadingAction(true));
const token = ctxGetUserToken(getState());
+ const placeholders = [
+ { replace: "account", value: accountIntId },
+ { replace: "todo_list", value: todoListIntId },
+ ];
- return fetch(TODOS_TODO_CREATE_ENDPOINT, token, null, HTTP_POST, todo)
+ return fetch(TODOS_TODO_CREATE_ENDPOINT, token, placeholders, HTTP_POST, todo)
.then((response) => {
...
};
}
So, the question is: how do I pass accountIntId and todoListIntId to createTodoThunk()?
Ideally, I'd like to create a method in TodoForm that actually wraps createTodoThunk passing it the two arguments accountIntId and todoListIntId.
Unfortunately, after a lot of try and fails, I'm writing here because I'm not able to find a proper solution.
Any ideas?

Related

React Context API - get updated state value

I am experimenting with React context api,
Please check someComponent function where I am passing click event (updateName function) then state.name value update from GlobalProvider function
after updated state.name it will reflect on browser but not getting updated value in console ( I have called console below the line of click function to get updated value below )
Why not getting updated value in that console, but it is getting inside render (on browser) ?
Example code
App function
<GlobalProvider>
<Router>
<ReactRouter />
</Router>
</GlobalProvider>
=== 2
class GlobalProvider extends React.Component {
state = {
name: "Batman"
};
render() {
return (
<globalContext.Provider
value={{
name: this.state.name,
clickme: () => { this.setState({ name: "Batman 2 " }) }
}}
>
{this.props.children}
</globalContext.Provider>
);
}
}
export default GlobalProvider;
=== 3
const SomeComponent = () => {
const globalValue = useContext(globalContext);
const updateName = ()=> {
globalValue.clickme();
console.log(globalValue.name ) //*** Here is my concern - not getting updated value here but , getting updated value in browser
}
return (
<div onClick={(e)=> updateName(e) }>
{globalValue.name}//*** In initial load display - Batman, after click it display Batman 2
</div>) }
React state isn't an observer like Vue or Angular states which means you can't get updated values exactly right after changing them.
If you want to get the updated value after changing them you can follow this solution:
class A extends Component {
state = {
name: "Test"
}
updateName = () => {
this.setState({name: "Test 2"}, () => {
console.log(this.state.name) // here, name has been updated and will return Test 2
})
}
}
So, you need to write a callback function for the clickme and call it as below:
class GlobalProvider extends React.Component {
state = {
name: "Batman"
};
render() {
return (
<globalContext.Provider
value={{
name: this.state.name,
clickme: (callback) => { this.setState({ name: "Batman 2 " }, () => callback(this.state.name)) }
}}
>
{this.props.children}
</globalContext.Provider>
);
}
}
export default GlobalProvider;
And for using:
const SomeComponent = () => {
const globalValue = useContext(globalContext);
const updateName = ()=> {
globalValue.clickme((name) => {
console.log(name) // Batman 2
});
}
return (
<div onClick={(e)=> updateName(e) }>
{globalValue.name}//*** In initial load display - Batman, after click it display Batman 2
</div>)
}

Fetching data from server through Redux (Action & Reducer) fails to store the data in the State

I'm trying to fetch data through Redux (with actions & reducers) and store it in a ReactTable
Here is the Table :
// MisleadLeadsTable
import React from "react";
import "react-table-v6/react-table.css";
import ReactTable from "react-table-v6";
import { connect } from "react-redux";
import {
getLeadsNotValid,
updateSpecificNotValidLead
} from "../../actions/leads";
import Spinner from "../layout/Spinner";
class MisleadLeadsTable extends React.Component {
constructor(props) {
super();
const { getLeadsNotValid } = props;
// Going to get data from the Server
// Call the Action and use the Reducer
getLeadsNotValid();
// Later put the data in the state
this.state = {
data: []
};
this.renderEditable = this.renderEditable.bind(this);
}
componentDidMount() {
// TODO
const { leadsNotValid } = this.props;
this.setState({
data: leadsNotValid
});
}
// Edit the cells
renderEditable(cellInfo) {
return (
<div
style={{ backgroundColor: "#fafafa" }}
contentEditable
suppressContentEditableWarning
onBlur={e => {
const data = [...this.state.data];
data[cellInfo.index][cellInfo.column.id] = e.target.innerHTML;
this.setState({ data });
}}
dangerouslySetInnerHTML={{
__html: this.state.data[cellInfo.index][cellInfo.column.id]
}}
/>
);
}
render() {
// loading data or not
const { loadingData } = this.props;
// This "data" should hold the fetched data from the server
const { data } = this.state;
return (
<div>
{loadingData ? (
<Spinner />
) : (
<div>
<ReactTable
data={data}
columns={[
{
Header: "Business Name",
accessor: "BusinessName"
// Cell: this.renderEditable
}
]}
defaultPageSize={10}
className="-striped -highlight"
/>
<br />
</div>
)}
</div>
);
}
}
const mapStateToProps = state => ({
loadingData: state.leadReducer.loadingData,
leadsNotValid: state.leadReducer.leadsNotValid
});
const mapDispatchToProps = { getLeadsNotValid, updateSpecificNotValidLead };
export default connect(mapStateToProps, mapDispatchToProps)(MisleadLeadsTable);
However when I try to store the data in the State (in componentDidMount) it always comes back empty , and when the table is being rendered it gets an empty array.
It is crucial to store the data in the State because I'm trying to implement an editable table.
The data is stored in leadsNotValid , and if I do :
<ReactTable
data={leadsNotValid} // Notice !! Changed this
columns={[
{
Header: "Business Name",
accessor: "BusinessName"
// Cell: this.renderEditable
}
]}
defaultPageSize={10}
className="-striped -highlight"
/>
Then the data is presented successfully to the user , however it's not in the State of the component.
How can I put the leadsNotValid in the State using setState ?
Here are the Action & Reducer if it's needed (THEY WORK GREAT !) :
Action :
import axios from "axios";
import {
REQUEST_LEADS_NOT_VALID,
REQUEST_LEADS_NOT_VALID_SUCCESS,
UPDATED_SUCCESSFULLY_A_NOT_VALID_LEAD_THAT_NOW_IS_VALID,
UPDATE_A_SINGLE_NOT_VALID_LEAD
} from "./types";
export const updateSpecificNotValidLead = updatedLead => async dispatch => {
dispatch({
type: UPDATE_A_SINGLE_NOT_VALID_LEAD
});
const config = {
headers: {
"Content-Type": "application/json"
}
};
const body = JSON.stringify({ updatedLead });
const res = await axios.post(
".......API/Something1/....",
body,
config
);
if (res !== null && res.data !== null) {
dispatch({
type: UPDATED_SUCCESSFULLY_A_NOT_VALID_LEAD_THAT_NOW_IS_VALID
});
}
};
export const getLeadsNotValid = () => async dispatch => {
dispatch({
type: REQUEST_LEADS_NOT_VALID
});
const res = await axios.get(".......API/Something2/....");
if (res !== null && res.data !== null) {
dispatch({
type: REQUEST_LEADS_NOT_VALID_SUCCESS,
payload: res.data
});
}
};
Reducer :
import {
GET_MAIN_LEADS_SUCCESS,
REQUEST_MAIN_LEADS,
RELOAD_DATA_MAIN_LEADS_TABLE,
REQUEST_LEADS_NOT_VALID,
REQUEST_LEADS_NOT_VALID_SUCCESS,
UPDATE_A_SINGLE_NOT_VALID_LEAD,
UPDATED_SUCCESSFULLY_A_NOT_VALID_LEAD_THAT_NOW_IS_VALID
} from "../actions/types";
const initialState = {
mainLeadsClients: [],
loadingData: null, // general loader
reloadMainLeadTable: 0,
reloadMisleadTable: 0,
leadsNotValid: []
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case REQUEST_LEADS_NOT_VALID:
return {
...state,
loadingData: true
};
case REQUEST_LEADS_NOT_VALID_SUCCESS:
return {
...state,
loadingData: false,
leadsNotValid: payload
};
case UPDATE_A_SINGLE_NOT_VALID_LEAD:
return {
...state,
loadingData: true
};
case UPDATED_SUCCESSFULLY_A_NOT_VALID_LEAD_THAT_NOW_IS_VALID:
return {
...state,
reloadMisleadTable: state.reloadMisleadTable + 1,
loadingData: false
};
// ... more
default:
return state;
}
}
You may have to write super(props) instead of super() in order to access props in the constructor.

React/Redux TypeError: this.state.dryRedBottles.map is not a function

I have a container component that fetches data to a Rails API but can't successfully iterator over that data without getting the following error;
TypeError: this.state.dryRedBottles.map is not a function
This was caused by the following code;
render() {
let searchResults = this.state.dryRedBottles.map((bottle) => <SearchResults key={bottle} name={bottle}/>)
As you can see in the code above, I am setting a variable equal to an iteration over this.state.dryRedBottles, which should map every bottle object to the presentational component SearchResults.
I also created a function, generateSearchResults to debug this.props and this.state. this.state.dryRedBottles is by default an empty array, but it's updated to be an array of objects. Since iterators like .map or .forEach only work on arrays, I tried to mitigate this on my Rails server;
def create
#wine_bottles = WineBottle.all
if params[:dryRedBottles][:fetchingRedDry] == true
#red_dry_bottles = []
#wine_bottles.each do |bottle|
if (bottle.w_type == 'red') & (bottle.dry == true)
bottle = [bottle] if !bottle.is_a?(Array)
#red_dry_bottles.push(bottle)
end
end
render json: #red_dry_bottles
else
nil;
end
end
I made sure each JSON object was push inside of an array, so at least this.state.dryRedBottles would return this; [[{}], [{}], [{}]].
My question is: what is causing this error?
What workarounds can I leverage to successfully use searchResults?
Below is my container component in its full glory;
class Red extends Component {
constructor(props) {
super(props);
this.state = {
// helps monitor toggling
redDryClick: false,
redBothClick: false,
redSweetClick: false,
fetchingRedDry: false,
fetchingRedSweet: false,
dryRedBottles: []
};
};
handleSweetRequest = (event) => {
event.preventDefault();
this.setState(prevState => ({
redDryClick: !prevState.redDryClick,
redBothClick: !prevState.redBothClick
}));
}
handleDryRequest = (event) => {
event.preventDefault();
this.setState(prevState => ({
redSweetClick: !prevState.redSweetClick,
redBothClick: !prevState.redBothClick,
fetchingRedDry: !prevState.fetchingRedDry
}));
}
componentDidUpdate(){
if (this.state.fetchingRedDry === true) {
let redDryState = Object.assign({}, this.state);
this.props.fetchDryReds(redDryState);
// this.props.dryRedBottles.length > this.state.dryRedBottles.length
if (this.props.dryRedBottles !== this.state.dryRedBottles ) {
this.setState({ dryRedBottles: this.props.dryRedBottles });
}
}
debugger;
}
handleBothRequest = (event) => {
event.preventDefault();
this.setState(prevState => ({
redDryClick: !prevState.redDryClick,
redSweetClick: !prevState.redSweetClick
}));
}
generateSearchResults = () => {
debugger;
if ( Array.isArray(this.props.dryRedBottles) ) {
this.props.dryRedBottles.map((bottle) => {
debugger;
return bottle;
})
}
}
render() {
let searchResults = this.state.dryRedBottles.map((bottle) => <SearchResults key={bottle} name={bottle}/>)
return (
<div>
<h2>Welcome to... Red</h2>
<FormControlLabel
control={
<Switch
// configuring #material-ui Switch componanet
value="hidden"
color="primary"
id="redSweet"
disableRipple
// handles previous State + redux + API call
onChange={this.handleSweetRequest}
disabled={this.state.redSweetClick}
/>
}
label="Sweet"
/>
<FormControlLabel
control={
<Switch
// configuring #material-ui Switch componanet
// value="hidden"
value="RedDry"
color="primary"
id="redDry"
disableRipple
// handles previous State + redux + API call
onChange={(event) => this.handleDryRequest(event)}
disabled={this.state.redDryClick}
/>
}
label="Dry"
/>
<FormControlLabel
control={
<Switch
// configuring #material-ui Switch componanet
value="hidden"
color="primary"
id="redBoth"
disableRipple
// handles previous State + redux + API call
onChange={this.handleBothRequest}
disabled={this.state.redBothClick}
/>
}
label="Both"
/>
<div>
{searchResults}
</div>
</div>
)
}
}
function mapStateToProps(state) {
return {
dryRedBottles: state.redWineReducer
};
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
fetchDryReds: fetchDryReds
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Red);
Below is my actionCreator;
export function fetchDryReds(redDryState) {
return (dispatch) => {
// debugger;
// dispatch({ type: 'LOADING_DRY_REDS' });
return fetch('http://localhost:3001/wine_bottles', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'},
body: JSON.stringify({dryRedBottles: redDryState})})
.then(response => response.json())
.then(dryRedBottles => {
dispatch({ type: 'FETCH_DRY_REDS', dryRedBottles })});
}
}
Below is my reducer;
export default function redWineReducer (state={}, action) {
switch (action.type) {
case 'FETCH_DRY_REDS':
// debugger;
return action.dryRedBottles
default:
return state;
}
}
This is the array of objects I am attempting to iterate over;
the initial state is an object... not an array so:
export default function redWineReducer (state={}, action) {
change it to:
export default function redWineReducer (state=[], action) {

React.js: props.state is blank(null)

I want to Todo List in use React.js + Redux.
I make reducer file:
import { ADD_POST, REMOVE_POST } from "../actions/index.jsx";
const initialState = {
title: "",
content: ""
};
export default function Post(state = initialState, action) {
switch (action.type) {
case ADD_POST:
return [
...state,
{
id: action.id,
title: action.title,
content: action.content
}
];
case REMOVE_POST:
return state.filter(({ id }) => id !== action.id);
default:
return state;
}
}
And, I edit App.js :
class App extends Component {
render() {
return (
<div className="App">
<Input />
<List posts={this.props.allPosts} />
</div>
);
}
}
const mapStateToProps = state => {
return {
allPosts: [state.title, state.content]
};
};
export default connect(mapStateToProps, null)(App);
And, List Component is...:
render() {
return (
<div>
<ul>
{this.props.posts.map((post, index) => (
<Item {...post} key={index} />
))}
</ul>
</div>
);
}
}
I am experiencing the error "Can not read property 'map' of undefined" and can not proceed.
How can I fix it?
I'm referring to multiple sources, but I'm having difficulty because I can only see text for one 'text' state, and two sources like 'title' and 'content' states.
-------_FIX
I fix error, but props.state is blank.
I add input tag with texts but it not change everything.
-------Actions
export const ADD_POST = "ADD_POST";
export const REMOVE_POST = "REMOVE_POST";
let nextId = 0;
export function addPost(title, content) {
return {
type: ADD_POST,
id: nextId++,
title,
content
};
}
export function removePost(id) {
return {
type: REMOVE_POST,
id
};
}
I think you're confusing with the data type of your state. The below snippet might work for you. I've kept your state as an array of posts with initialState being an empty array.
So in your reducer file, initialise the initialState as:
import {
ADD_POST,
REMOVE_POST
} from "../actions/index.jsx";
const initialState = [];
export default function Post(state = initialState, action) {
switch (action.type) {
case ADD_POST:
return [
...state,
{
id: action.id,
title: action.title,
content: action.content
}
];
case REMOVE_POST:
return state.filter(({
id
}) => id !== action.id);
default:
return state;
}
}
In App.js, in the function mapStateToProps, map allPosts to state which is an array.
class App extends Component {
render() {
return (
<div className="App">
<Input />
<List posts={this.props.allPosts} />
</div>
);
}
}
const mapStateToProps = state => {
return {
allPosts: state
};
};
export default connect(mapStateToProps, null)(App);

Component not able to re-render when state is changed

I am new to react. I want to confirm the input JSON is valid or not and show that on scree. The action ValidConfiguration is being fired and reducer is returning the new state but the smart component add-config-container is not being re-rendered
Here are my files:
Action
import {
VALID_CONFIGURATION,
INVALID_CONFIGURATION,
SAVE_CONFIGURATION,
START_FETCHING_CONFIGS,
FINISH_FETCHING_CONFIGS,
EDIT_CONFIGURAION
} from '../constants';
function validateConfiguration(jsonString) {
try {
JSON.parse(jsonString);
} catch (e) {
return false;
}
return true;
}
export function isConfigurationValid(state) {
if (validateConfiguration(state.jsonText)) {
return({type: VALID_CONFIGURATION, state : state});
} else {
return({type: INVALID_CONFIGURATION, state : state});
}
}
export function fetchConfiguration() {
return ({type : START_FETCHING_CONFIGS});
}
export function finishConfiguration(configs) {
return ({type : FINISH_FETCHING_CONFIGS, configs: configs});
}
export function editConfiguration(index) {
return ({type : EDIT_CONFIGURATION, index : index});
}
export function saveConfiguration(config) {
return ({type: SAVE_CONFIGURATION, config : config});
}
Container component
import React, {Component} from 'react';
import {Button, Input, Snackbar} from 'react-toolbox';
import {isConfigurationValid, saveConfiguration} from '../../actions/config';
import { connect } from 'react-redux';
import style from '../../theme/layout.scss';
class AddConfigContainer extends Component {
constructor(props) {
super(props);
this.state = {jsonText: '', key: '', valid: false, showBar : true};
}
handleChange(text, value) {
this.setState({[text]: value});
}
handleSnackbarClick() {
this.setState({ showBar: false});
};
handleSnackbarTimeout() {
this.setState({ showBar: false});
};
render() {
let {onValid} = this.props;
return (
<div>
<h4>Add Configs</h4>
<span>Add configs in text box and save</span>
<Input type='text' label='Enter Key'
value={this.state.key} onChange={this.handleChange.bind(this, 'key')} required/>
<Input type='text' multiline label='Enter JSON configuration'
value={this.state.jsonText} onChange={this.handleChange.bind(this, 'jsonText')} required/>
<div>IsJSONValid = {this.state.valid ? 'true': 'false'}</div>
<Snackbar action='Dismiss'
label='JSON is invalid'
icon='flag'
timeout={2000}
active={ this.state.showBar }
onClick={this.handleSnackbarClick.bind(this)}
onTimeout={this.handleSnackbarTimeout.bind(this)}
type='accept'
class = {style.loader}
/>
<Button type="button" label = "Save Configuration" icon="add" onClick={() => {onValid(this.state)}}
accent
raised/>
</div>
);
}
}
const mapStateToProps = (state) => {
let {
jsonText,
key,
valid
} = state;
return {
jsonText,
key,
valid
};
};
const mapDispatchToProps = (dispatch) => {
return {
onValid : (value) => dispatch(isConfigurationValid(value)),
saveConfiguration: (config) => dispatch(saveConfiguration(config))
}
};
export default connect(mapStateToProps, mapDispatchToProps)(AddConfigContainer);
Reducer
import assign from 'object.assign';
import {VALID_CONFIGURATION, INVALID_CONFIGURATION} from '../constants';
const initialState = {
jsonText : '',
key : '',
valid : false,
showBar: false,
configs: [json],
activeConfig : {},
isFetching: false
};
export default function reducer(state = initialState, action) {
if (action.type === VALID_CONFIGURATION) {
return (assign({}, state, action.state, {valid: true}));
} else if (action.type === INVALID_CONFIGURATION) {
return assign({}, state, action.state, {valid: false});
} else {
return state;
}
}
I think your component does re-render, but you never actually use the valid value from props (i.e. this.props.valid). You only use this.state.valid, but that is not changed anywhere in the code. Note that Redux won't (and can't) change the component's internal state, it only passes new props to the component, so you need to use this.props.valid to see the change happen. Essentially, you should consider whether you need valid to exist in the component's state at all. I don't think you do, in this case all the data you have in state (except perhaps showBar) doesn't need to be there and you can just take it from props.
If you do need to have them in state for some reason, you can override e.g. componentWillReceiveProps to update the component's state to reflect the new props.

Categories

Resources