I've built a custom Input component which is simply a wrapper for the HTML input element. Here's the critical code, which I've simplified for posting here:
// #flow
import React, { useState } from 'react';
type Props = {
value?: string,
onChange: Function
};
const Input = ((props: Props) => {
const [ currentValue, setCurrentValue ] = useState(!!props.value ? props.value : '');
const handleChange = (event: SyntheticInputEvent<EventTarget>) => {
setCurrentValue(event.target.value);
props.onChange(event);
};
return <input type='text'
value={currentValue}
onChange={handleChange} />;
});
I wrote a bunch of React Testing Library tests for this and they all pass fine. But when I implemented this component in a web page, the initial value failed to appear. I solved the problem by dropping the currentValue code and just using props.value instead. That solves it. But I'm most curious why this approach above fails to display the initial value.
Look at this code, I did use prop-types
Related
I'm trying to test a TextInput by checking its value after entering a string, but it's not working. Any help would be appreciated.
Here's the code:
import React from 'react'
import { TextInput } from 'react-native'
import { fireEvent, render } from "#testing-library/react-native"
test('<TextInput/>, () => {
const { getByTestId } = render( <TextInput testID="input" /> );
const input = getByTestId("input");
fireEvent.changeText(input, "123");
expect(input.value).toBe("123");
})
The test fails with the message:
Expected: "123"
Received: undefined
I think your example is not working because you are not controlling the input. Try adding a value and an onChangeText function. Something like:
function Example() {
const [value, setValue] = useState('')
return <TextInput value={value} onChangeText={setValue} testID="input" />
}
test('Should apply the value when changing text', () => {
const { getByTestId } = render(<Exampler />);
const input = getByTestId('input');
fireEvent.changeText(input, '123');
expect(input.props.value).toBe('123');
});
Also, you need to check input.props.value instead of input.value
Hope it helps.
I'm going to suggest a few things to assist you to get to the solution,
Are you sure TextInput has a prop called testID? You use getByTestId subsequently, which needs a data-testid value on the element. So please make sure of this first.
You can try finding the element in another way. Probably use getByPlaceholderText or similar.
Once you can get the element properly, and after the fireEvent, the value should certainly be updated, and assertion would succeed.
I want to create a slate.js-based editor component that keeps it's state in markdown. Slate.js docs keep repeating how simple serializing and deserializing state into md should be, but they don't provide an actual way to do it.
I tried achieving such editor with remark-slate-transformer in a very straight-forward way , based on these two examples: remark-slate-transformer, slate:
import React, { useMemo, useState } from "react";
import { createEditor } from "slate";
import { Slate, Editable, withReact } from "slate-react";
import stringify from "remark-stringify";
import unified from "unified";
import markdownParser from "remark-parse";
import { remarkToSlate, slateToRemark } from "remark-slate-transformer";
import { withHistory } from "slate-history";
function markdown2slate(markdown) {
const processor = unified().use(markdownParser).use(remarkToSlate);
return processor.processSync(markdown).result;
}
function slate2markdown(slate) {
const processor = unified().use(slateToRemark).use(stringify);
const ast = processor.runSync({ type: "root", children: slate });
return processor.stringify(ast);
}
export const App = () => {
const editor = useMemo(() => withHistory(withReact(createEditor())), []);
const [value, setValue] = useState("**initialText**");
const onChange = (newVal) => {
setValue(slate2markdown(newVal));
};
const editorValue = markdown2slate(value);
return (
<div className="wrapper">
<Slate editor={editor} value={editorValue} onChange={onChange}>
<Editable />
</Slate>
</div>
);
};
export default App;
sandbox here
But this doesn't work very well. I expect the initial text to appear in bold, but it doesn't. The cursor keeps jumping back to position 0 on every keystroke. Also, when I delete the string (value becomes ''), the editor breaks.
What is the correct, hassle-free way of making an editor component with state stored as markdown?
I'm not sure why you would absolutely want to store the editor state as Markdown, but this just cannot and will not work: you can't simply swap Slate internal state to something different than what it expects, its own object model, and expect it to work.
What you can do is to deserialize Markdown content into a Slate state object, feed that to the editor, let Slate do its thing while you edit and then serialize back to Markdown to do whatever you need to do with it, store it, send it, etc.
I have this kind of jsx and I want to pass number value from SendNumberPage to CheckNumberPage.
App.js
<EditNumberPage/>
<br/>
<SendNumberPage/>
<br/>
<CheckNumberPage/>
SendNumberPage.js
function onChangeHandler(event) {
setState({
...state,
number: event.target.value
})
}
I tried using React.createContext but it didn't work for me. Please Help
sendNumberPage.js
const [state, setState] = useState(
{
number: '+99979787'
}
)
const NumberContext = React.createContext()
return (
<NumberContext.Provider value={state.number}>
<div>
....
....
</div>
</NumberContext.Provider>
)
checkNumberPage.js
const CheckNumberPage = () => {
const value = useContext(NumberContext)
console.log(value)
return (
.......
)
}
Console says Attempted import error: 'NumberContext' is not exported from './SendNumberPage'.
Depending on how complex your app is you may want to do this in different ways.
Using react context api is a good way to do it, and it is scalable and suitable for all app sizes.
You should check out the react tutorials for that.
If your app is very small (1 layer) and you just want a 'quick fix' you could pass a change listener callback to one component and update the props in the other component.
<EditNumberPage/>
<br/>
<SendNumberPage onChange={(n) => {setNumber(n)} />
<br/>
<CheckNumberPage number={number}/>
I am trying to create a custom widget for Netlify CMS that connects to the redux store to be updated when a file widget has a new path. My widget will then determine the size of the file to set it's own value.
So far, I have simply cloned the starter widget and changed src/Conrol.js to this:
import React from 'react';
import { connect } from 'react-redux' //Added this line
class Control extends React.Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
forID: PropTypes.string,
value: PropTypes.node,
classNameWrapper: PropTypes.string.isRequired,
}
static defaultProps = {
value: '',
}
render() {
const {
forID,
value,
onChange,
classNameWrapper,
} = this.props;
return (
<input
type="text"
id={forID}
className={classNameWrapper}
value={value || ''}
onChange={e => onChange(e.target.value)}
/>
);
}
}
//Added everything below
const mapStateToProps = (state, ownProps) => {
const fileUrl = "test";
return { value: fileUrl };
};
export default connect(mapStateToProps, null, null, { withRef: true })(Control);
With these changes I get the following error:
Element ref was specified as a string (wrappedInstance) but no owner
was set. This could happen for one of the following reasons: 1. You
may be adding a ref to a functional component 2. You may be adding a
ref to a component that was not created inside a component's render
method 3. You have multiple copies of React loaded See
https://reactjs.org/link/refs-must-have-owner for more information.
I can't figure out what I'm doing wrong. As for the reasons given in the error:
I'm working with a class component.
I didn't even touch the component rendering. I only added the connect().
I ran npm ls react as suggested at the link provided in the error. I got two copies of react, but one was "deduped".
I have searched far and wide for an answer, but have not found one. I am new to react/redux, so I'm hoping someone can point me in the right direction.
I have a simple react component which takes in a prop which is an Object.
I am trying to pass this in to a dispatch call to get it added to a list.
This works fine if I pass in a static value for the description field.
But fails when I use the dynamic field. I have checked via console log and the dynamic field definitely has a value and it is of type String as expected.
Why does it work when I use a static value (done solely for testing) for now but fails when I use the dynamic value?
Don't see any issues with my store, action, reducer setup cos it updates state fine with a static value.
Please advice. Thank you.
Experiencing this issue inside the ExpenseForm component existing within AddExpensePage where I
am using the expense value being passed in to perform the dispatch.
This is the working example with the static String value.
import React from "react";
import ExpenseForm from "./ExpenseForm";
import {connect} from "react-redux";
const AddExpensePage = (props) =>
<div>
<h1>Add Expense</h1>
<ExpenseForm onSubmit={(expense) => {
// This is what I want to do but unable to cos of the issue mentioned with description value thus breaking it out as follows.
// props.dispatch(addExpense(expense))
console.log(expense.description) // I do get a value like 'sample input' thus no issue with value
console.log(typeof expense.description) // it is of type String as expected
// breaking out the expense to be able to hard code value for description.
const tempFakeExpense = {
description: 'HARD_CODED_STRING_WHICH_WORKS', // expense.description will not update
note: expense.note,
amount: expense.amount,
createdAt: expense.createdAt
}
props.dispatch(addExpense(tempFakeExpense))
props.history.push('/');
}}/>
</div>
export default connect()(AddExpensePage);
The above doesn't work if I use the dynamic value as follows.
Meaning, if list had 10 items, it remains as 10. It doesn't append list to make it 11.
const actualExpense = {
description: expense.description, // not using hard coded value, thus not updating. Why?
note: expense.note,
amount: expense.amount,
createdAt: expense.createdAt
}
Expecting the issue to exist in above code. Adding the Expense component code below for reference in case there is some form of red herring.
There is no Redux in this component cos the state values are only for input tracking and not used anywhere else. Thus sticking to plain state/ setstate style of state management.
Note: The value got passed correctly to above component thus this state management works fine for ExpenseForm component and it doesn't need Redux to work in this component.
import React from 'react';
import moment from "moment";
import {SingleDatePicker} from "react-dates";
import 'react-dates/lib/css/_datepicker.css';
class ExpenseForm extends React.Component {
state = {
description: '',
note: '',
amount: '',
createdAt: moment(),
focused: false
}
render() {
return (
<div>
<form onSubmit={(e) => {
e.preventDefault();
if(this.state.description && this.state.amount) {
this.props.onSubmit({
description: this.state.description,
amount: parseFloat(this.state.amount),
note: this.state.note,
createdAt: this.state.createdAt.valueOf()
})
}
}}>
<input
type='text'
placeholder='description...'
autoFocus
value={this.state.description}
onChange={e => {
this.setState(() => ({
description: e.target.value
}))
}}
/>
{/* Setups for other fields */}
<button>Add Expense</button>
</form>
</div>
);
}
}
export default ExpenseForm;